package net.gdface.facelog;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import gu.simplemq.redis.JedisPoolLazy;
import gu.simplemq.redis.RedisFactory;
import gu.simplemq.redis.RedisTable;
import gu.sql2java.exception.ObjectRetrievalException;
import net.gdface.facelog.db.DeviceBean;
import net.gdface.facelog.db.DeviceGroupBean;
import net.gdface.facelog.db.PersonBean;
import net.gdface.facelog.db.PersonGroupBean;

/**
 * 临时密码管理
 * @author guyadong
 *
 */
public class TempPwdManagement implements ServiceConstant {
	private final DaoManagement dm;
	private final RedisTable<TmpwdTargetInfo> tempPwdTable;
	private final RedisTable<String> tempPwdTabler;	
	public TempPwdManagement(DaoManagement dao) {
		this.dm = checkNotNull(dao,"dao is null");
		this.tempPwdTable =  RedisFactory.getTable(TABLE_TMP_PWD, JedisPoolLazy.getDefaultInstance());
		this.tempPwdTabler =  RedisFactory.getTable(TABLE_TMP_PWD_R, JedisPoolLazy.getDefaultInstance());
	}
	/**
	 * 根据持续时间(分钟)加上当前时间计算出到期时间
	 * @param duration 持续时间(分钟) 
	 * @return 到期时间
	 */
	public static Date getExpiryDate(int duration){
		checkArgument(duration >0,"duration must > 0");
		Date date = new Date(); //取时间 
		Calendar   calendar = Calendar.getInstance(); 
		calendar.setTime(date); 
		calendar.add(Calendar.MINUTE,duration);  
		return calendar.getTime();  
	}
	/**
	 * 为targetId指定的目标创建临时密码对象
	 * @param targetId 目标ID
	 * @param targetType 目标类型 
	 * @param expiryDate 临时密码到期时间
	 * @return 临时密码
	 */
	@SuppressWarnings("unused")
	synchronized String createTempPwd(int targetId,TmpPwdTargetType targetType,Date expiryDate){
		try {
			switch (checkNotNull(targetType,"targetType is null")) {
			case USER_GROUP_ID:	
			{
				PersonGroupBean bean = dm.daoGetPersonGroupChecked(targetId);				
				break;
			}
			case DEVICE_GROUP_ID:
			{
				DeviceGroupBean bean = dm.daoGetDeviceGroupChecked(targetId);
				break;
			}
			case USER_ID:
			{
				PersonBean bean = dm.daoGetPersonChecked(targetId);
				break;
			}
			case DEVICE_ID:
			{
				DeviceBean bean = dm.daoGetDeviceChecked(targetId);
				break;
			}
			default:
				throw new IllegalArgumentException(String.format("UNSUPPORTED target type %s", targetType));
			}
			if(null == expiryDate){
				expiryDate = getExpiryDate(DEFAULT_TEMP_PWD_DURATION);
			}
			TmpwdTargetInfo target = new TmpwdTargetInfo(targetType, targetId, expiryDate);
			String targetKey = target.asKey();
			// 旧密码
			String oldpwd = tempPwdTabler.get(targetKey);
			if(oldpwd != null){
				// 如果存在旧密码则删除旧密码
				tempPwdTable.remove(oldpwd);
				tempPwdTabler.remove(targetKey);
			}
			Random random = new Random();
			// 6位数字密码
			String pwd = String.format("%06d",random.nextInt(1000000));
			while (tempPwdTable.containsKey(pwd)){
				pwd = String.format("%06d",random.nextInt(1000000));
			}
			tempPwdTable.set(pwd, target, true);			
			tempPwdTabler.set(targetKey,pwd, true);
			// 设置过期时间
			tempPwdTable.expire(pwd,expiryDate);
			tempPwdTabler.expire(targetKey,expiryDate);
			return pwd;
		} catch (ObjectRetrievalException e) {
			throw new IllegalArgumentException(String.format("INVALID targetId %d for %s",targetId,targetType));
		}
	}
	synchronized TmpwdTargetInfo getTempPwd(String pwd){
		return Strings.isNullOrEmpty(pwd) ? null : tempPwdTable.get(pwd);
	}
	private boolean belong(int deviceGroupId,int deviceId){
		DeviceBean deviceBean = dm.daoGetDevice(deviceId);
		List<Integer> parents = null == deviceBean 
					? ImmutableList.<Integer>of()
					: dm.daoToPrimaryKeyListFromDeviceGroups(dm.daoListOfParentForDeviceGroup(deviceBean.getGroupId()));
		return parents.contains(deviceGroupId);
	}
	/**
	 * 判断指定用户是否可以在指定设备上通行
	 * @param personId
	 * @param deviceId
	 * @return 允许通行返回{@code true},否则返回{@code false}
	 */
	private boolean permitPerson(int personId,int deviceId){
		Set<PersonBean> features = dm.daoGetPersonsPermittedOnDevice(deviceId, false,  null, null);
		return Collections2.transform(features, dm.daoCastPersonToPk).contains(personId);
	}
	private boolean permitPersonGroup(int personGroupId,int deviceId){
		DeviceBean deviceBean = dm.daoGetDevice(deviceId);
		if(null != deviceBean){			
			return dm.daoGetPersonGroupsPermittedBy(deviceBean.getGroupId(),false).contains(personGroupId);			
		}
		return false;
	}
	TmpwdTargetInfo getTargetInfo4PwdOnDevice(String pwd,int deviceId){
		TmpwdTargetInfo targetInfo = getTempPwd(pwd);
		boolean permit = new DeviceFilter(deviceId).apply(targetInfo);
		return permit ? targetInfo : null ;
	}
	boolean isValidTempPwd4Device(String pwd,int deviceId){
		return null != getTargetInfo4PwdOnDevice(pwd,deviceId);
	}
	/**
	 * 获取指定设备上所有的临时密码
	 * @param deviceId 设备ID
	 * @return {@link TmpPassword}对象列表,没有则返回空表
	 */
	List<TmpPassword> getAllPasswordsOnDevice(Integer deviceId){		
		final List<TmpPassword> pwds = Lists.newLinkedList();
		if(deviceId != null){
			for(Entry<String, TmpwdTargetInfo> entry:tempPwdTable.valuesV("*", new DeviceFilter(deviceId)).entrySet()){
				TmpwdTargetInfo v = entry.getValue();
				pwds.add(new TmpPassword(v.getTargetType(), v.getTargetId(), v.getExpiryDate(), entry.getKey()));
			}
		}
		return pwds;
	}
	
	private class DeviceFilter implements Predicate<TmpwdTargetInfo>{
		private final int deviceId;
		DeviceFilter(int deviceId) {
			this.deviceId = deviceId;
		}
		@Override
		public boolean apply(TmpwdTargetInfo targetInfo) {
			if(targetInfo == null){
				return false;
			}
			switch(targetInfo.getTargetType()){
			case USER_GROUP_ID:
				return permitPersonGroup(targetInfo.getTargetId(),deviceId);
			case DEVICE_GROUP_ID:
				return belong(targetInfo.getTargetId(),deviceId);
			case USER_ID:
				return permitPerson(targetInfo.getTargetId(), deviceId);
			case DEVICE_ID:
				return targetInfo.getTargetId() == deviceId;
			default:
				throw new IllegalArgumentException(String.format("UNSUPPORTED target type %s", targetInfo.getTargetType()));
			}
		}
	}
}
